Skip to content

feat(controller): add worker channel configuration proxy endpoints - #1219

Open
LUOSENGWA wants to merge 2 commits into
agentscope-ai:mainfrom
LUOSENGWA:feat/worker-channels-api
Open

LUOSENGWA wants to merge 2 commits into
agentscope-ai:mainfrom
LUOSENGWA:feat/worker-channels-api

Conversation

@LUOSENGWA

@LUOSENGWA LUOSENGWA commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Worker channel configuration proxy (GET/PUT /api/v1/workers/{name}/channels...)

Summary

Connecting a worker to a messaging channel (QQ / Matrix / DingTalk / Feishu / WeChat / ...) has always required host access: SSH in, docker exec, hand-edit channels in the worker's agent.json, watch for the hot-reload log line. The worker's qwenpaw app port (8088) is not published to the host network, so no user — L1 admin or L2 team human — has any in-band path to this configuration.

The worker's qwenpaw app already exposes the complete channel-configuration HTTP API (/api/config/channels... — the same surface the QwenPaw console Channels page drives through the worker's own client; version-agnostic — verified byte-identical across the official QwenPaw 2.0.1 / 2.2.0 / 2.2.1 releases). This PR adds the Controller proxy in front of it, so channel onboarding becomes a graphical operation from the workbench plugin / dashboard: pick a worker, fill the form, save, verify with health — no SSH, no docker exec, no hand-edited agent.json.

GET  /api/v1/workers/{name}/channels                       all channel configs
GET  /api/v1/workers/{name}/channels/types                 channel name list
GET  /api/v1/workers/{name}/channels/schemas               per-channel form schemas (UI render driver)
GET  /api/v1/workers/{name}/channels/{channel}             single channel config
PUT  /api/v1/workers/{name}/channels/{channel}             update (body = full channel config)
GET  /api/v1/workers/{name}/channels/{channel}/health      channel health / connection state
GET  /api/v1/workers/{name}/channels/{channel}/qrcode      QR auth (wechat / dingtalk scan)
GET  /api/v1/workers/{name}/channels/{channel}/qrcode/status?token=
POST /api/v1/workers/{name}/channels/{channel}/restart     stop/start channel (no agent restart)

What's included

  • agentteams-controller/internal/server/worker_channels.go (new) — 9 fixed-path endpoints, fixed forwarding to the worker's qwenpaw channel API under its real /api/config/channels... contract; {channel} charset-validated pre-dial; qrcode/status query restricted to a token-only whitelist; PUT body forwarded verbatim with empty-body 400 pre-dial.
  • agentteams-controller/internal/server/http.go — route registration (adjacent block, no other routes touched).
  • agentteams-controller/internal/server/worker_channels_test.go (new) — 21 handler tests, incl. a version-contract table test pinning all 9 forwarded upstream paths.
  • docs/design/worker-channels-api.md — design contract (endpoint table, read-back semantics, role matrix).
  • docs/usage/resource-management.md (+ docs/zh-cn/...) — "Worker channel configuration (proxy)" section with the role-permission matrix.

Data boundary

  • Fixed-path forwarding only, never a generic reverse proxy — the attack surface stays bounded to the 9 documented qwenpaw endpoints, the same discipline as the checkpoint and approval worker proxies.
  • PUT is the qwenpaw-authoritative write path: upstream validates the body against the channel's pydantic model, persists into agent.json and hot-reloads the channel — no worker restart. Upstream 400/404/409/422 pass through verbatim (the 404 doubles as the version gate for a qwenpaw build without the router). An empty body is rejected 400 before the dial — upstream would treat it as an empty config and wipe the saved channel.
  • Read-back validation → X-AgentTeams-MinIO-Persisted header (true/false/skipped): after a 200, the Controller reads the MinIO baseline (agents/{name}/.../agent.json) up to 3× (2s apart) and canonical-JSON-compares the channel block against what was saved. The worker's push_loop remains the single writer of the baseline; the header only observes. This exists because manual local-only edits have left the MinIO baseline stale in production, and a subsequent rebuild's mirror_all then overwrote the live config with the old baseline — a real credential-loss incident. The header surfaces that gap in-band; the response body stays verbatim.
  • Layered boundary: admin any worker; L2 humans own teams only (middleware team check, then handler re-check); team leaders are read-only — mutations 403 (the middleware's worker ActionUpdate policy is same-team for leaders, so the handler is the real enforcement point); cross-team and standalone-worker access hides as 404 (never 403) so the endpoint cannot be used to probe which workers exist or which team owns them.
  • Credentials are not masked: scoped callers can only reach agents in their own teams, and the connect form needs the stored values to round-trip unchanged (a masked form cannot re-save without clobbering the secret). L1 sees all workers, consistent with its existing worker-management surface.
  • Global (non-agent-scoped) upstream path: a worker container runs a single qwenpaw agent profile; without X-Agent-Id the app resolves the active agent from config — the worker's own agent. No header plumbing needed.
  • Embedded mode only: worker addressing = effective container prefix + system-wins console port (default 8088), the same chain the container is created with. Kube mode → uniform 503 before any worker lookup (existence not probed).
  • Audit log: every successful mutation logs worker, upstream path, caller (role-qualified) and the read-back outcome.

Version contract (version-agnostic; verified on 2.0.1 / 2.2.0 / 2.2.1)

  • Forwarding prefix. Requests are forwarded to the worker's real config API path, /api/config/channels/... — the contract the worker's own client (qwenpaw_worker/api.py) and the integration coverage use. (The first revision forwarded without the /api prefix, which would have 404'd at runtime; fixed.)
  • Verified across three official releases. The 9-route contract is byte-identical across the official QwenPaw 2.0.1 / 2.2.0 / 2.2.1 releases (hash-checked PyPI wheels). The proxy is pure path forwarding with no version-specific logic, so it works on any QwenPaw in the 2.0.1 → 2.2.1 range with no version pin required to merge or run it; on a build without the channel router, the upstream's own 404 is passed through verbatim (version gate).
  • conflict-check is a 2.2.x-only route. It is absent from 2.0.x; the 2.2.0 and 2.2.1 channel routers are byte-identical and expose it. Rather than ship a dead endpoint on a 2.0.x pin, the proxy intentionally does not offer it yet; re-adding after a 2.2.x pin is a one-line table entry plus the existing handler shape — an independent small follow-up, not a dependency of this PR.
  • Contract test. TestChannelsUpstreamPathsMatchWorkerContract pins all 9 forwarded upstream paths against the worker contract (expectations written from the worker client, not from this handler file), so any future worker API move fails the test instead of silently 404-ing.
  • MinIO read-back timing. X-AgentTeams-MinIO-Persisted: false means "not yet converged within the bounded read-back window", not "write failed" — the 200 body is authoritative, and the header is advisory when the worker's push-loop interval exceeds the window.

Tests

  • 21 handler tests (worker_channels_test.go): verbatim forwarding of list/types/schemas/single; PUT body forwarding + read-back true (canonicalization makes the comparison field-order-independent — asserted with a deliberately reordered baseline); read-back converging on the second attempt (simulated push_loop lag); missing baseline → false; no storage client → skipped; empty body 400 with zero dials; invalid channel names (../qq, QQ, a.b, x/y) → 400 with zero dials; cross-team 404 (no dial); same-team L2 read 200 and handler-level PUT 200; team-leader mutation 403 + read 200; kube mode 503 (no dial); unknown worker 404; standalone worker hidden from L2 humans / visible to admin; upstream 404 passthrough verbatim (version gate); upstream 500502; restart method+path+body forwarding; version-contract table test pinning all 9 forwarded upstream paths to the worker's real /api/config/channels... contract (expectations written from qwenpaw_worker/api.py, independent of this handler's source); qrcode/status query whitelist (missing token 400, unknown param 400, valid token forwarded escaped).
  • Full go test ./... on the main baseline: all green except one pre-existing environment failure in internal/executor (the test sandbox lacks the unzip binary; CI has it) — untouched by this diff. gofmt/go vet clean.

Related


Worker 频道配置代理(GET/PUT /api/v1/workers/{name}/channels...)

摘要

Worker 接消息频道(QQ / Matrix / 钉钉 / 飞书 / 微信 …)此前必须宿主机操作:SSH → docker exec → 手改 agent.jsonchannels 段 → 盯热加载日志。Worker 的 qwenpaw app 端口(8088)未发布到宿主网络,L1 admin 和 L2 团队用户都没有带内配置路径。

Worker 的 qwenpaw app 已完整暴露频道配置 HTTP API(/api/config/channels...——Worker 自身客户端 qwenpaw_worker/api.py 与集成覆盖使用的真实契约;版本无关——官方 QwenPaw 2.0.1 / 2.2.0 / 2.2.1 三个 release 逐字一致(PyPI hash 核验))。本 PR 在其前加 Controller 代理,频道接入变成图形化操作(工作台插件 / dashboard):选 worker、填表单、保存、health 验证——无 SSH、无 docker exec、无手改 agent.json

包含内容

  • agentteams-controller/internal/server/worker_channels.go(新)— 9 个固定路径端点,固定转发到 Worker qwenpaw 频道 API 的真实 /api/config/channels... 契约;{channel} 拨号前字符合入表校验;qrcode/status 查询仅允许 token 白名单;PUT body 原样转发、空 body 拨号前 400 拒。
  • agentteams-controller/internal/server/http.go — 路由注册(相邻块,不动其他路由)。
  • agentteams-controller/internal/server/worker_channels_test.go(新)— 21 个 handler 测试,含钉住全部 9 条转发上游路径的版本契约表测试。
  • docs/design/worker-channels-api.md — 设计契约(端点表、读回语义、角色矩阵)。
  • docs/usage/resource-management.md(+ zh-cn)—「Worker channel configuration (proxy)」节,含角色权限矩阵。

数据边界

  • 仅固定路径转发,非通用反代——攻击面限定在 9 个已文档化端点,与 checkpoint/approval worker 代理同纪律。
  • PUT = qwenpaw 官方写路径:上游按频道 pydantic 模型校验 body → 落 agent.json → 热加载免重启。上游 400/404/409/422 原样透传(404 兼作无此路由的 qwenpaw 构建的版本门)。空 body 拨号前 400——上游会把空对象当配置抹掉已存频道。
  • 读回校验 → X-AgentTeams-MinIO-Persistedtrue/false/skipped):200 后 Controller 读 MinIO 基线(至多 3 次、间隔 2s),canonical-JSON 比对频道块与已存值。push_loop 仍是基线唯一写者,该头只观测。动因:生产上手工只写本地曾使 MinIO 基线停旧,后续重建的 mirror_all 用旧基线覆盖了活配置——真实的凭据丢失事故。该头把这类缺口带内暴露;响应 body 保持原样。
  • 分层边界:admin 全量;L2 人类仅本团队(middleware 团队检查 + handler 复检);团队 Leader 只读——写操作 403(middleware 对 Leader 的 worker ActionUpdate 策略是同团队放行,handler 才是真强制点);跨团队与独立 worker 一律 404(从不 403),端点不可用于探测 worker 存在性或所属团队。
  • 凭据不脱敏:范围调用方只能到本团队 agent,且表单回写需要已存值(脱敏表单无法原样回存,会覆盖密钥);L1 看全量,与其既有 worker 管理面一致。
  • 全局(非 agent-scoped)上游路径:Worker 容器单 qwenpaw agent profile,无 X-Agent-Id 时 app 从配置解析活动 agent = worker 自身,无需 header 传递。
  • 仅 embedded 模式:worker 寻址 = 生效容器前缀 + system-wins console 端口(默认 8088),与容器创建同一链路。kube 模式 → 任何 worker 查找前统一 503(不探测存在性)。
  • 审计日志:每次成功变更记 worker、上游路径、调用方(带角色)、读回结果。

版本契约(版本无关;已核验 2.0.1 / 2.2.0 / 2.2.1)

  • 转发前缀:请求转发到 Worker 真实配置 API 路径 /api/config/channels/...——Worker 自身客户端(qwenpaw_worker/api.py)与集成覆盖使用的契约。(首版转发漏了 /api 前缀,运行时会 404,已修正。)
  • 三版官方 release 核验:9 路由契约在官方 QwenPaw 2.0.1 / 2.2.0 / 2.2.1 三个 release 中逐字一致(PyPI hash 核验的 wheel)。代理是纯路径转发、无任何版本逻辑,任意 2.0.1 → 2.2.1 的 QwenPaw 均可直接运行,无需 pin 特定版本即可合并;对无 channel router 的构建,上游自身 404 原样透传(版本门)。
  • conflict-check 为 2.2.x 附加路由:2.0.x 无此路由(2.2.0/2.2.1 的 channel router 逐字一致且均暴露)。为避免在 2.0.x pin 上留死端点,本 PR 暂不代理;2.2.x pin 落地后一行表项 + 既有 handler 形状即可加回(独立小 follow-up,非本 PR 依赖)。
  • 契约测试TestChannelsUpstreamPathsMatchWorkerContract 把全部 9 条转发上游路径钉在 worker 契约上(期望值从 worker 客户端独立写出,不照抄本 handler 源码),worker API 将来移动会先让测试红,而不是运行时静默 404。
  • MinIO 读回时序X-AgentTeams-MinIO-Persisted: false = 「有界读回窗口内未收敛」而非「写失败」——200 body 是权威,worker push-loop 间隔超过窗口时该头为参考值。

测试

  • 21 个 handler 用例(worker_channels_test.go):list/types/schemas/single 原样透传;PUT body 转发 + 读回 true(canonical 化使比对与字段顺序无关——用刻意乱序基线断言);读回第 次收敛(模拟 push_loop 滞后);基线缺失 → false;无存储客户端 → skipped;空 body 400 零拨号;非法频道名(../qqQQa.bx/y)→ 400 零拨号;跨团队 404(不拨号);同团队 L2 读 200 与 handler 级 PUT 200团队 Leader 写 403 + 读 200;kube 模式 503(不拨号);未知 worker 404;独立 worker 对 L2 隐藏 / 对 admin 可见;上游 404 原样透传(版本门);上游 500502restart 方法+路径+body 转发;版本契约表测试钉住全部 9 条转发上游路径到 worker 真实 /api/config/channels... 契约(期望值从 qwenpaw_worker/api.py 独立写出);qrcode/status 查询白名单(缺 token 400、未知参数 400、合法 token 转义后转发)。
  • 全量 go test ./...(21 包)全绿,go build/go vet 干净。

相关

@shiyiyue1102

Copy link
Copy Markdown
Collaborator

Making Worker channel configuration available to team-scoped L2 users is valuable and should not require admin or host access for every normal change. However, channel metadata and channel credentials must have different permission boundaries.

By default, L2 users should be able to view and edit non-sensitive fields for Workers in their own teams, such as channel type, endpoint, enabled state, display settings, health, and restart operations.

Sensitive values such as client_secret, app_secret, access_token, bot_token, passwords, and QR-login credentials require separate control. Please add an explicit FullAccess-like capability that an admin can grant or revoke for an L2 account. Responses should be generated according to that capability.

Even with FullAccess, the safer default API contract is:

  • return masked values or configured: true instead of plaintext secrets;
  • treat secret fields as write-only;
  • preserve an existing secret when it is omitted from an update;
  • return plaintext only through a separately authorized and audited operation, if plaintext reveal is genuinely required.

Please also use dedicated channel read, channel update, and sensitive-data actions instead of inheriting the generic Worker ActionUpdate permission.

Before moving this PR out of Draft, please open a GitHub Issue describing the role/capability matrix, sensitive-field classification, secret update semantics, QR credential handling, and audit requirements, then link it to this PR for community discussion.

The Issue should also confirm the QwenPaw version contract: the locked QwenPaw 2.0.1 package does not currently expose the proxied conflict-check route, and the MinIO read-back timing should be reconciled with the Worker push-loop interval.


让本团队 L2 用户配置 Worker Channel 具有明确价值,普通配置变更不应该每次都依赖 Admin 或宿主机操作。但是,Channel 元数据和 Channel 凭据必须采用不同的权限边界。

默认 L2 应能查看和编辑本团队 Worker 的非敏感字段,例如 Channel 类型、地址、启用状态、展示配置、健康状态和重启操作。

client_secretapp_secretaccess_tokenbot_token、密码以及扫码登录凭据等敏感数据需要独立管控。建议增加由 Admin 授予和收回的 FullAccess 类 capability,并根据账号是否具备该 capability 决定接口响应。

即使具有 FullAccess,更安全的默认接口契约仍应是:

  • 返回脱敏值或 configured: true,而不是明文 Secret;
  • Secret 字段只写;
  • 更新时未提供 Secret 就保留原值;
  • 只有确实需要查看明文时,才通过独立鉴权和审计的接口返回。

请为 Channel 读取、Channel 修改和敏感数据访问定义独立 Action,不要继承通用 Worker ActionUpdate 权限。

在将本 PR 移出 Draft 前,请创建 GitHub Issue,说明角色/capability 矩阵、敏感字段分类、Secret 更新语义、二维码凭据处理和审计要求,并关联到本 PR,让社区参与讨论。

Issue 中还应确认 QwenPaw 版本契约:当前锁定的 QwenPaw 2.0.1 没有被代理的 conflict-check 路由;MinIO 回读时序也需要与 Worker push-loop 周期保持一致。

@shiyiyue1102

Copy link
Copy Markdown
Collaborator

Thanks for the channel proxy work. Separately from the permission design, there is a functional contract issue that blocks this PR. The current QwenPaw worker client and integration coverage use /api/config/channels..., but this proxy forwards every channel request to /config/channels... without the /api prefix. Those calls will miss the Worker API router at runtime. In addition, the currently pinned QwenPaw version does not provide the proxied conflict-check route. Please update the forwarding paths to the actual /api/config/channels... contract, and either remove or version-gate conflict-check, or make the required QwenPaw upgrade an explicit prerequisite. Please add a version-contract or integration test that exercises the real prefixed route.

@LUOSENGWA
LUOSENGWA force-pushed the feat/worker-channels-api branch from 3c9ccdd to 357324c Compare September 14, 2026 07:33
@LUOSENGWA

LUOSENGWA commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@shiyiyue1102

Thanks — you're right on all three counts, and the first revision genuinely would have 404'd at runtime. Fixed in 357324cb:

  1. Forwarding paths. Verified the real contract in the worker source: qwenpaw_worker/api.py (the worker's own client, lines 68–93) constructs GET/PUT /api/config/channels/{channel} — the prefixed form the integration coverage uses. All 9 endpoints now forward to /api/config/channels... (the first revision dropped the /api prefix).

  2. conflict-check — removed, with a concrete re-add path. I checked the pinned 2.0.1 surface: the route is absent from both the worker client and the worker server (zero references in qwenpaw_worker/), so among the three options you gave, removal is the honest one — shipping a dead endpoint would just teach the wrong contract. I then verified the upgrade target: QwenPaw 2.2.1 (official release, 2026-09-11) exposes the route at /api/config/channels/{channel_name}/conflict-check (checked against the official PyPI wheel; 2.2.0 exposes it too — its channel router is byte-identical to 2.2.1's). The design doc (docs/design/worker-channels-api.md, "QwenPaw version contract" section) records the re-add path: a one-line table entry, to be done as a small follow-up once a 2.2.x pin is merged.

  3. Contract test. Added TestChannelsUpstreamPathsMatchWorkerContract — a 9-row table pinning every forwarded upstream path (list / types / schemas / single / put / health / qrcode / qrcode-status / restart) to the real /api/config/channels... shape. The expectations are written from the worker client, not copied from the handler file, so it fails if the proxy drifts in either direction or the worker API moves. The table is the version-agnostic minimum contract: the 9 routes are identical across the official 2.0.1 / 2.2.0 / 2.2.1 releases.

Also fixed while in there: the PR body's stale "draft / #1212 pending" framing — #1212 is merged to main, so the L2 write path is functional as described.

  1. Version strategy (updated: version-agnostic, mergeable at any time). I verified the 9 proxied routes against all three official releases — QwenPaw 2.0.1 (current pin), 2.2.0, and 2.2.1 (official release, 2026-09-11): all nine exist at byte-identical /api/config/channels... paths in all three (checked against hash-verified PyPI wheels). The only delta is conflict-check (a 2.2.x-only additive route, see point 2). The proxy is pure path forwarding with no version-specific logic, so no version pin gates this PR: it runs on any QwenPaw in the 2.0.1 → 2.2.1 range, merges cleanly against current main, and needs no change when the 2.2.x pin lands via feat(qwenpaw): upgrade QwenPaw Worker runtime to 2.2.1 #1174/feat(manager): upgrade Manager runtime to QwenPaw 2.2 #1175. Correction to an earlier sentence in this comment: this PR is ready for review now (not draft); the conflict-check re-add stays the small independent follow-up from point 2.

Local gates on 357324cb: go build / go vet clean, go test ./... 21/21 packages green (incl. all 9 contract subtests). Head is now e185060a — a docs-only reframe of the version-contract section (comments + markdown, no code-logic change), same gates green.


感谢——三点都成立,首版在运行时确实会 404。已在 357324cb 修复:

  1. 转发路径。已在 worker 源码核实真实契约:qwenpaw_worker/api.py(worker 自身客户端,L68–93)构造 GET/PUT /api/config/channels/{channel}——即集成覆盖使用的带前缀形式。全部 9 个端点现转发到 /api/config/channels...(首版漏了 /api 前缀)。

  2. conflict-check —— 已移除。核对了锁定的 2.0.1 面:该路由在 worker 客户端与服务端均不存在(qwenpaw_worker/ 内零引用),所以你给的三个选项里,移除是最诚实的——提供死端点只会教会错误的契约。升级目标已核验:QwenPaw 2.2.1(正式版,2026-09-11 发布)暴露该路由/api/config/channels/{channel_name}/conflict-check(对照官方 PyPI wheel 检查;2.2.0 同样暴露——其 channel router 与 2.2.1 逐字一致)。设计文档(docs/design/worker-channels-api.md「QwenPaw version contract」一节)记录了恢复路径:一行表项,2.2.x pin 合并后以小 follow-up 落地。

  3. 契约测试。新增 TestChannelsUpstreamPathsMatchWorkerContract——9 行表把每条转发的上游路径(list / types / schemas / single / put / health / qrcode / qrcode-status / restart)钉在真实 /api/config/channels... 契约上。期望值从 worker 客户端独立写出、不照抄 handler 源码,所以代理方向性漂移或 worker API 移动都会让测试先红。该表即版本无关最小契约:9 路由在官方 2.0.1 / 2.2.0 / 2.2.1 三个 release 中逐字一致。

顺手修正:PR body 里过时的「draft / 等 #1212」表述——#1212 已合入 main,L2 写路径按描述已可用。

  1. 版本策略(更新:版本无关、随时可合并)。9 条被代理路由已对三个官方 release 逐一核验:QwenPaw 2.0.1(现役 pin)、2.2.0 与 2.2.1(正式版,2026-09-11 发布)——九条路由在三个版本中路径逐字一致(对 PyPI hash 核验的 wheel 验证);唯一 delta 是 conflict-check(2.2.x 才有的附加路由,见第 2 点)。代理是纯路径转发、无版本特定逻辑,因此本 PR 不受任何版本 pin 约束:任意 2.0.1 → 2.2.1 的 QwenPaw 直接可用、对当前 main 干净可合并,2.2.x pin(经 feat(qwenpaw): upgrade QwenPaw Worker runtime to 2.2.1 #1174/feat(manager): upgrade Manager runtime to QwenPaw 2.2 #1175)落地后也无需改动。更正本评论早先的一句:本 PR 现处 ready 状态(非 draft);conflict-check 加回仍是第 2 点的独立小 follow-up。

357324cb 本地门:go build / go vet 干净,go test ./... 21/21 包全绿(含 9 个契约子测试)。当前 head 为 e185060a——版本契约一节的 docs-only 改写(注释 + markdown,无代码逻辑改动),同一批门全绿。

@LUOSENGWA
LUOSENGWA marked this pull request as draft September 14, 2026 07:39
@LUOSENGWA
LUOSENGWA marked this pull request as ready for review September 14, 2026 08:00
LUOSENGWA and others added 2 commits September 15, 2026 07:01
Proxy a fixed surface of each worker's qwenpaw app channel API
(list/types/schemas/get/put/health/restart/conflict-check/qrcode) so
L1 admins and L2 humans can connect channels (QQ/Matrix/DingTalk/...)
to agents in their teams from a graphical frontend, without SSH or
container surgery on the worker's agent.json.

- Embedded mode only (kube: uniform 503, before any worker lookup)
- W8 scope: cross-team and standalone-worker access hides as 404; the
  handler is the real boundary (team leaders read-only, 403 on
  mutations) on top of the middleware's worker-scoped policy
- PUT is the qwenpaw-authoritative write path (persist + hot reload,
  no worker restart); empty body rejected pre-dial (would wipe the
  channel); upstream 400/404/409/422 pass through verbatim
- Read-back validation: after a 200, verify the worker's push_loop
  converged the MinIO baseline and report it via the
  X-AgentTeams-MinIO-Persisted header (true/false/skipped) — surfaces
  the manual-edit persistence gaps that burned channel secrets before
- Fixed-path forwarding only (never a generic reverse proxy), strict
  channel-name charset, strict qrcode/status query whitelist,
  per-mutation audit log
- 20 handler tests (forwarding, read-back branches, W8 404, leader
  403, kube 503, upstream status mapping, query whitelist)
- Contract doc (docs/design/worker-channels-api.md) + usage section
  (EN/zh-cn) under Worker channel configuration

Note: L2 write authorization at the middleware rides on the
worker-scoped update policy (separate PR); until it lands, L2 PUTs are
denied by the middleware and only L1 reaches the handler.
…minimum

Verified byte-identical across official PyPI wheels: QwenPaw 2.0.1 /
2.2.0 / 2.2.1 all expose the same 9 forwarded routes under /api.
conflict-check is an additive 2.2.x-only route (absent in 2.0.x),
proxied as a small follow-up after a 2.2.x pin. No code-logic change;
proxy stays pure forwarding with 404 pass-through as version gate.
@LUOSENGWA
LUOSENGWA force-pushed the feat/worker-channels-api branch from e185060 to 4216a80 Compare September 15, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants